//
// Created by xu on 2024/11/21.
//
#include <stdio.h>
#include <libavcodec/avcodec.h>
#include <libavutil/avutil.h>

// 错误处理
#define ERROR_BUF(ret) \
    char errbuf[1024]; \
    av_strerror(ret, errbuf, sizeof (errbuf));


typedef struct {
    const char *filename;
    int sampleRate;
    enum AVSampleFormat sampleFormat;
    AVChannelLayout chLayout;
} AudioEncodeSpec;

static int check_sample_fmt(const AVCodec *codec, enum AVSampleFormat sample_format) {
    const enum AVSampleFormat *p = codec->sample_fmts;
    while (*p != AV_SAMPLE_FMT_NONE) {
        if (*p == sample_format) return 1;
        p++;
    }
    return 0;
}


int encode(AVCodecContext *ctx, AVFrame *frame, AVPacket *pkt, FILE *outFile) {
    // 发送数据到编码器
    int ret = avcodec_send_frame(ctx, frame);
    if (ret < 0) {
        ERROR_BUF(ret);
        av_log(NULL, AV_LOG_INFO, "avcodec_send_frame error %s\n", errbuf);
        return ret;
    }
    while (1) {
        // 从编码器中获取编码后的数据
        ret = avcodec_receive_packet(ctx, pkt);
        // packet中已经没有数据了，需要重新发送数据到编码器
        if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
            return 0;
        } else if (ret < 0) {
            ERROR_BUF(ret);
            av_log(NULL, AV_LOG_INFO, "avcodec_receive_packet error %s\n", errbuf);
            return ret;
        }
        // 写入文件
        fwrite(pkt->data, 1, pkt->size, outFile);
        // 释放资源
        av_packet_unref(pkt);
    }
}

void aacEncode(AudioEncodeSpec *in, const char *outFilename) {
    FILE *inFile = NULL;
    FILE *outFile = NULL;
    const AVCodec *codec = NULL;
    AVCodecContext *ctx = NULL;
    AVFrame *frame = NULL; // 用于存放编码前的数据
    AVPacket *pkt = NULL; // 用于存放编码后的数据
    int ret = 0;

    // 获取编码器
    // codec = avcodec_find_encoder(AV_CODEC_ID_AAC);
    codec = avcodec_find_encoder_by_name("libfdk_aac");
    if (!codec) {
        av_log(NULL, AV_LOG_INFO, "avcodec_find_encoder error\n");
        return;
    }
    // 检查采样格式
    // 内置的aac-codec不支持AV_SAMPLE_FMT_S16，仅支持codec不支持AV_SAMPLE_FMT_FLTP
    if (!check_sample_fmt(codec, in->sampleFormat)) {
        av_log(NULL, AV_LOG_INFO, "Encode does not support sample format %s\n",
               av_get_sample_fmt_name(in->sampleFormat));
        return;
    }
    // 创建上下文
    ctx = avcodec_alloc_context3(codec);
    if (!ctx) {
        av_log(NULL, AV_LOG_INFO, "avcodec_alloc_context3 error\n");
        return;
    }

    // 获取支持的采样格式
    // const enum AVSampleFormat *fmts = NULL;
    // ret = avcodec_get_supported_config(ctx, codec,
    //                                    AV_CODEC_CONFIG_SAMPLE_FORMAT, 0,
    //                                    (const void **) &fmts, NULL);
    // av_log(NULL, AV_LOG_INFO, "avcodec_get_supported_config %d\n", ret);
    // return;


    // 设置参数
    ctx->sample_fmt = in->sampleFormat;
    ctx->sample_rate = in->sampleRate;
    ctx->ch_layout = in->chLayout;
    // 比特率
    ctx->bit_rate = 32000;
    // 规格
    ctx->profile = FF_PROFILE_AAC_HE_V2;

    // 打开编码器
    // 如果是想设置一些libfdk_aac特有的参数（比如vbr），可以通过options参数传递
    // AVDictionary *options = NULL;
    // av_dict_set(&options, "vbr", "1", 0);
    // ret = avcodec_open2(ctx, codec, &options);
    ret = avcodec_open2(ctx, codec, NULL);
    if (ret < 0) {
        ERROR_BUF(ret);
        av_log(NULL, AV_LOG_INFO, "avcodec_open2 error %s\n", errbuf);
        goto end;
    }

    // 创建AVFrame
    frame = av_frame_alloc();
    if (!frame) {
        av_log(NULL, AV_LOG_INFO, "av_frame_alloc error\n");
        goto end;
    }
    // 样本帧数量（由frame_size决定）
    frame->nb_samples = ctx->frame_size;
    // 采样格式
    frame->format = ctx->sample_fmt;
    // 声道布局
    frame->ch_layout = ctx->ch_layout;
    // 创建AVFrame内部缓冲区
    ret = av_frame_get_buffer(frame, 0);
    if (ret < 0) {
        ERROR_BUF(ret);
        av_log(NULL, AV_LOG_INFO, "av_frame_get_buffer error %s\n", errbuf);
        goto end;
    }

    // 创建AVPacket
    pkt = av_packet_alloc();
    if (!pkt) {
        av_log(NULL, AV_LOG_INFO, "av_packet_alloc error \n");
        goto end;
    }

    // 打开文件
    inFile = fopen(in->filename, "rb");
    if (inFile == NULL) {
        av_log(NULL, AV_LOG_INFO, "File open error %s\n", in->filename);
        goto end;
    }
    outFile = fopen(outFilename, "wb");
    if (outFile == NULL) {
        av_log(NULL, AV_LOG_INFO, "File open error %s\n", outFilename);
        goto end;
    }

    // 开始编码
    while ((ret = fread(frame->data[0], 1, frame->linesize[0], inFile)) > 0) {
        // 最后一次读文件数据时，有可能并没有填满frame的缓冲区
        if (ret < frame->linesize[0]) {
            // 每个样本的大小
            int bytes = av_get_bytes_per_sample(frame->format);
            // 改为真正有效的样本帧数量
            frame->nb_samples = ret / (frame->ch_layout.nb_channels * bytes);
        }
        // 编码
        if (encode(ctx, frame, pkt, outFile) < 0) {
            goto end;
        }
    }
    // flush编码器
    encode(ctx, NULL, pkt, outFile);

    end:
    fclose(inFile);
    fclose(outFile);
    if (frame){
        av_freep(&frame->data[0]);
        av_frame_free(&frame);
    }
    av_packet_free(&pkt);
    avcodec_free_context(&ctx);
}


int main(int argc, char *argv[]) {
    AVChannelLayout inChLayout = AV_CHANNEL_LAYOUT_STEREO;
    // 输入参数
    AudioEncodeSpec in;
    in.filename = "C:/Users/xu/Desktop/output.pcm";
    in.sampleFormat = AV_SAMPLE_FMT_S16;
    in.sampleRate = 44100;
    in.chLayout = inChLayout;
    aacEncode(&in, "C:/Users/xu/Desktop/output.aac");
    return 0;
}